package application;

import static org.lwjgl.opengl.GL11.GL_COLOR_BUFFER_BIT;
import static org.lwjgl.opengl.GL11.glClear;

import java.io.Serializable;

import game.Game;
import game.Inventory;

import org.lwjgl.input.Keyboard;
import org.lwjgl.input.Mouse;
import org.lwjgl.opengl.Display;

import application.menu.Menu;
import application.messages.Message;
import application.messages.commands.ConsoleCommand;
import application.messages.commands.GameCommand;
import application.messages.commands.MenuCommand;
import application.messages.commands.parameters.Coordinates2D;
import data.DataManager;

/**
 * Implements basic application.
 * It cares about opening menu or game.
 * 
 * @author Tomas Zima, Drahomir Karchnak
 */
public class Application implements Serializable
{
	/**
	 * Constructor.
	 */
	public Application()
	{
		// Initiallize all components
		mainWindow	= new Window(Configuration.WINDOW_WIDTH, Configuration.WINDOW_HEIGHT, Configuration.WINDOW_TITLE, Configuration.WINDOW_FULLSCREEN);
		menu		= new Menu();
		game		= new Game();
		console		= new Console();
		
		// Set default state of components
		console.stop();
		game.stop();
		menu.run();

		/*
		 * Create instance of DataManager.
		 * This class is a singleton a first call of getInstance() method
		 * will create instance and call constructor.
		 * 
		 * Constructor of DataManager is loading some files and it's good
		 * to do so when executing the application.
		 */
		DataManager.getInstance();
	}
	
	/**
	 * Run application.
	 */
	public void run()
	{
		// funkcni prehravani zvuku (zakomentovano, pri ladeni je to otravne)
		/*try
		{
			Audio soundStream = AudioLoader.getAudio("WAV", ResourceLoader.getResourceAsStream("res/Sounds/Improvizace.wav"));
			soundStream.playAsMusic(1.0f, 1.0f, true);
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}*/
		
		// Initialize some variables
		float	fps				= 0;
		int		count			= 0;
		long	lastFPSPrint	= System.currentTimeMillis();
		long	lastTime		= System.currentTimeMillis();
		
		// Until the end of the world
		while (mainWindow.running())
		{
			// Save time for FPS counter
			long _fpsTime0 = System.currentTimeMillis();

			// Read the whole stack of messages
			while (console.hasNewCommand())
			{
				// Get last message
				Message message = console.popMessage();
				
				// Serve the message
				switch ((ConsoleCommand) message.command)
				{
					// Send message to the game if Application cannot handle it
					default:
						game.enterNewConsoleCommand(message);
						break;
				}
			}
			
			/*
			 * Reads action from menu.
			 * (This is using ReverseMenuCommand).
			 */
			switch (menu.getAction())
			{
				// Nothing to do
				case NONE:
					break;

				case SAVE_GAME:
					Serialization.serialize(this.game, "saves/save.ser");
					break;
					
				case LOAD_GAME:
					this.game = Serialization.deserialize("saves/save.ser");
					
					if (this.game == null)
					{
						System.out.println("The game cannot be loaded.");
						System.exit(1);
					}
					
					break;
					
				// Create game
				case NEW_GAME:
					// Clear last command in menu
					menu.clearMenuCommand();
					
					// Generate message
					Message message = new Message();
					message.command = GameCommand.SYSTEM_CREATE_WORLD;
					message.position = new Coordinates2D(0, 0);

					// Switch from menu to the game
					menu.stop();
					game.enterNewMessage(message);
					game.run();
					break;
					
				// Exit application
				case EXIT:
					menu.clearMenuCommand();
					System.exit(0);
					break;
			}
			
			// Clear 
			glClear(GL_COLOR_BUFFER_BIT);
			
			// Check peripheries
			keyboardController();
			mouseController();
			
			// Update objects based on speed of the loop
			game.update(System.currentTimeMillis() - lastTime);
			
			// Save time
			lastTime = System.currentTimeMillis();
			
			// Redraw all components
			game.redraw();
			menu.redraw();
			console.redraw();

			// Refresh scene
			mainWindow.update();
			
			/*
			 * FPS counter.
			 * It is calculating average FPS rate and printing
			 * result to standard input for each 10 seconds.
			 */
			try
			{
				fps += 1000 / (System.currentTimeMillis() - _fpsTime0);
				count++;
				
				if (lastFPSPrint + 10000 < System.currentTimeMillis())
				{
					System.out.println("FPS: " + fps / count);
					
					lastFPSPrint = System.currentTimeMillis();
					fps = 0;
					count = 0;
				}
			}
			catch (ArithmeticException errHandler)
			{
				//
			}
		}
		
		// Called on close
		Display.destroy();
		Keyboard.destroy();
		Mouse.destroy();
	}
	
	/**
	 * Checks pressed keys and do action.
	 */
	private void keyboardController()
	{
		while (Keyboard.next())
		{
			// for inventory
			if (Keyboard.isKeyDown(Keyboard.KEY_TAB))
			{
				Inventory.getInstance().itemSelectRight();
			}
			
			// for console
			if (Keyboard.isKeyDown(Keyboard.KEY_F5))
			{
				if (game.isActive())
				{
					game.stop();
					console.run();
				}
				else
				{
					game.run();
					console.stop();
				}
			}
			
			// general purposes
			if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE))
			{				
				if (game.isActive())
				{
					if (game.isWorldCreated())
					{
						menu.run();
						game.stop();
					}
				}
				else
				if (menu.isActive())
				{
					if (menu.isSubScreenActivated())
					{
						menu.goOutOfSubScreen();
					}
					else
					{
						if (game.isWorldCreated())
						{
							menu.stop();
							game.run();
						}
					}
				}
				else
				if (console.isActive())
				{
					console.stop();
					game.run();
				}
			}

			if (Keyboard.isKeyDown(Keyboard.KEY_1))
			{
				this.game.lastType = DataManager.BlockType.BLOCK_DIRT;
			}

			if (Keyboard.isKeyDown(Keyboard.KEY_2))
			{
				this.game.lastType = DataManager.BlockType.BLOCK_STONE;
			}

			if (Keyboard.isKeyDown(Keyboard.KEY_3))
			{
				this.game.lastType = DataManager.BlockType.BLOCK_IRON;
			}

			if (Keyboard.isKeyDown(Keyboard.KEY_4))
			{
				this.game.lastType = DataManager.BlockType.BLOCK_GOLD;
			}

			if (Keyboard.isKeyDown(Keyboard.KEY_5))
			{
				this.game.lastType = DataManager.BlockType.BLOCK_CACTUS;
			}
			
			// for jumping
			if (Keyboard.isKeyDown(Keyboard.KEY_SPACE))
			{
				if (game.isActive())
				{
					Message msg			= new Message();
					msg.command			= GameCommand.PLAYER_JUMP;
					msg.position	= new Coordinates2D(0, 0);
					
					game.enterNewMessage(msg);
				}
			}
			
			// for console
			if (console.isActive())
			{
				if(Keyboard.getEventKeyState())
				{
					switch (Keyboard.getEventKey())
					{
						case Keyboard.KEY_A:		console.addNewCharacter('a'); break;
						case Keyboard.KEY_B:		console.addNewCharacter('b'); break;
						case Keyboard.KEY_C:		console.addNewCharacter('c'); break;
						case Keyboard.KEY_D:		console.addNewCharacter('d'); break;
						case Keyboard.KEY_E:		console.addNewCharacter('e'); break;
						case Keyboard.KEY_F:		console.addNewCharacter('f'); break;
						case Keyboard.KEY_G:		console.addNewCharacter('g'); break;
						case Keyboard.KEY_H:		console.addNewCharacter('h'); break;
						case Keyboard.KEY_I:		console.addNewCharacter('i'); break;
						case Keyboard.KEY_J:		console.addNewCharacter('j'); break;
						case Keyboard.KEY_K:		console.addNewCharacter('k'); break;
						case Keyboard.KEY_L:		console.addNewCharacter('l'); break;
						case Keyboard.KEY_M:		console.addNewCharacter('m'); break;
						case Keyboard.KEY_N:		console.addNewCharacter('n'); break;
						case Keyboard.KEY_O:		console.addNewCharacter('o'); break;
						case Keyboard.KEY_P:		console.addNewCharacter('p'); break;
						case Keyboard.KEY_Q:		console.addNewCharacter('q'); break;
						case Keyboard.KEY_R:		console.addNewCharacter('r'); break;
						case Keyboard.KEY_S:		console.addNewCharacter('s'); break;
						case Keyboard.KEY_T:		console.addNewCharacter('t'); break;
						case Keyboard.KEY_U:		console.addNewCharacter('u'); break;
						case Keyboard.KEY_V:		console.addNewCharacter('v'); break;
						case Keyboard.KEY_W:		console.addNewCharacter('w'); break;
						case Keyboard.KEY_X:		console.addNewCharacter('x'); break;
						case Keyboard.KEY_Y:		console.addNewCharacter('y'); break;
						case Keyboard.KEY_Z:		console.addNewCharacter('z'); break;
						
						case Keyboard.KEY_0:		console.addNewCharacter('0'); break;
						case Keyboard.KEY_1:		console.addNewCharacter('1'); break;
						case Keyboard.KEY_2:		console.addNewCharacter('2'); break;
						case Keyboard.KEY_3:		console.addNewCharacter('3'); break;
						case Keyboard.KEY_4:		console.addNewCharacter('4'); break;
						case Keyboard.KEY_5:		console.addNewCharacter('5'); break;
						case Keyboard.KEY_6:		console.addNewCharacter('6'); break;
						case Keyboard.KEY_7:		console.addNewCharacter('7'); break;
						case Keyboard.KEY_8:		console.addNewCharacter('8'); break;
						case Keyboard.KEY_9:		console.addNewCharacter('9'); break;
						
						case Keyboard.KEY_NUMPAD0:	console.addNewCharacter('0'); break;
						case Keyboard.KEY_NUMPAD1:	console.addNewCharacter('1'); break;
						case Keyboard.KEY_NUMPAD2:	console.addNewCharacter('2'); break;
						case Keyboard.KEY_NUMPAD3:	console.addNewCharacter('3'); break;
						case Keyboard.KEY_NUMPAD4:	console.addNewCharacter('4'); break;
						case Keyboard.KEY_NUMPAD5:	console.addNewCharacter('5'); break;
						case Keyboard.KEY_NUMPAD6:	console.addNewCharacter('6'); break;
						case Keyboard.KEY_NUMPAD7:	console.addNewCharacter('7'); break;
						case Keyboard.KEY_NUMPAD8:	console.addNewCharacter('8'); break;
						case Keyboard.KEY_NUMPAD9:	console.addNewCharacter('9'); break;
						
						case Keyboard.KEY_SPACE:	console.addNewCharacter(' '); break;
						case Keyboard.KEY_BACK:		console.removeLastCharacter(); break;
						case Keyboard.KEY_RETURN:	console.executeCommand(); break;
					}					
				}
			}
		}
		
		if (lastMove + 10 < System.currentTimeMillis())
		{
			if (Keyboard.isKeyDown(Keyboard.KEY_DOWN))
			{
				if (game.isActive())
				{
					Message msg			= new Message();
					msg.command			= GameCommand.PLAYER_MOVE_DOWN;
					msg.position	= new Coordinates2D(0, 0);
					
					game.enterNewMessage(msg);
				}
				
				if (menu.isActive())
				{
					Message msg			= new Message();
					msg.command			= MenuCommand.SELECT_DOWN;
					
					menu.enterNewMessage(msg);
				}
			}
			
			if (Keyboard.isKeyDown(Keyboard.KEY_UP))
			{
				if (game.isActive())
				{
					Message msg			= new Message();
					msg.command			= GameCommand.PLAYER_MOVE_UP;
					msg.position	= new Coordinates2D(0, 0);
		
					game.enterNewMessage(msg);				
				}
				
				if (menu.isActive())
				{
					Message msg			= new Message();
					msg.command			= MenuCommand.SELECT_UP;
					
					menu.enterNewMessage(msg);
				}
			}
			
			//if (Keyboard.isKeyDown(Keyboard.KEY_LEFT))
			if (Keyboard.getEventKey() == Keyboard.KEY_LEFT)
			{
				if (game.isActive())
				{
					if (Keyboard.getEventKeyState())
					{
						Message msg		= new Message();
						msg.command		= GameCommand.PLAYER_MOVE_LEFT;
						msg.position	= new Coordinates2D(0, 0);
			
						game.enterNewMessage(msg);
					}
					else
					{
						Message msg			= new Message();
						msg.command			= GameCommand.PLAYER_STOP_MOVE_LEFT;
						msg.position		= new Coordinates2D(0, 0);
						
						game.enterNewMessage(msg);
					}
				}
			}
			
			//if (Keyboard.isKeyDown(Keyboard.KEY_RIGHT))
			if (Keyboard.getEventKey() == Keyboard.KEY_RIGHT)
			{
				if (game.isActive())
				{
					if (Keyboard.getEventKeyState())
					{
						Message msg			= new Message();
						msg.command			= GameCommand.PLAYER_MOVE_RIGHT;
						msg.position	= new Coordinates2D(0, 0);
			
						game.enterNewMessage(msg);				
					}
					else
					{
						Message msg		= new Message();
						msg.command		= GameCommand.PLAYER_STOP_MOVE_RIGHT;
						msg.position	= new Coordinates2D(0, 0);
			
						game.enterNewMessage(msg);
					}
				}
			}
			
			if (Keyboard.isKeyDown(Keyboard.KEY_RETURN))
			{
				if (menu.isActive())
				{
					Message msg			= new Message();
					msg.command			= MenuCommand.SELECT_ITEM;

					menu.enterNewMessage(msg);
				}
			}
			
			if (Keyboard.isKeyDown(Keyboard.KEY_LSHIFT))
			{
				if (game.isActive())
				{
					Message msg		= new Message();
					msg.position	= new Coordinates2D(0, 0);
					msg.command		= GameCommand.SHOOT_LEFT;
					
					game.enterNewMessage(msg);
				}
			}
			else
			if (Keyboard.isKeyDown(Keyboard.KEY_RSHIFT))
			{
				if (game.isActive())
				{
					Message msg		= new Message();
					msg.position	= new Coordinates2D(0, 0);
					msg.command		= GameCommand.SHOOT_RIGHT;
					
					game.enterNewMessage(msg);
				}
			}
			
			lastMove = System.currentTimeMillis();
		}
	}
	
	/**
	 * Checks mouse and performs actions
	 * (usually generate messages).
	 */
	private void mouseController()
	{
		while (Mouse.next())
		{
			if (Mouse.isButtonDown(0))
			{
				if (game.isActive())
				{
					if (lastBlockBreak + 50 < System.currentTimeMillis())
					{
						lastBlockBreak = System.currentTimeMillis();
						
						int x = Mouse.getX();
						int y = Mouse.getY();
						
						Message msg			= new Message();
						msg.command			= GameCommand.REMOVE_BLOCK;
						msg.position		= new Coordinates2D(x, y);
						
						game.enterNewMessage(msg);
					}
				}
				
				if (menu.isActive())
				{
					//
				}
			}
			else
			if (Mouse.isButtonDown(1))
			{
				if (game.isActive())
				{
					int x = Mouse.getX();
					int y = Mouse.getY();
					
					Message msg			= new Message();
					msg.command			= GameCommand.PLACE_NEW_BLOCK;
					msg.position	= new Coordinates2D(x, y);
					
					game.enterNewMessage(msg);
				}
				
				if (menu.isActive())
				{
					//
				}
			}
		}
	}
	
	// Current window
	private Window		mainWindow;
	private Console		console;
	
	// Components
	private Menu	menu;
	private Game	game;
	
	// testing purposes
	private long	lastMove = System.currentTimeMillis();

	private long	lastBlockBreak = System.currentTimeMillis();
}
